package com.zst.common.http;


import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.http.nio.AsyncResponseConsumer;
import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder;
import org.apache.hc.core5.reactor.IOReactorConfig;

import javax.net.ssl.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 封装apache.asynchttpclient的io多路复用通信能力的HTTP客户端
 */
public class IOMultiplexHttpClient {
    private CloseableHttpAsyncClient client = null;
    private final int timeout;

    public IOMultiplexHttpClient(int timeout) {
        this.timeout = timeout;
    }

    public CompletableFuture<SimpleHttpResponse> doGet(String url, Map<String, String> headers) {
        AsyncRequestBuilder producerBuilder = AsyncRequestBuilder.get(url);

        if (headers != null && !headers.isEmpty()) {
            headers.forEach(producerBuilder::setHeader);
        }

        return execute(producerBuilder);
    }

    public CompletableFuture<SimpleHttpResponse> doPost(String url, Map<String, String> headers, String jsonBody) {
        AsyncRequestBuilder producerBuilder = AsyncRequestBuilder.post(url);

        if (headers != null && !headers.isEmpty()) {
            headers.forEach(producerBuilder::setHeader);
        }

        producerBuilder.setEntity(jsonBody, ContentType.APPLICATION_JSON);

        return execute(producerBuilder);
    }

    public CompletableFuture<SimpleHttpResponse> doPost(String url, Map<String, String> headers, Map<String, String> params) {
        AsyncRequestBuilder producerBuilder = AsyncRequestBuilder.post(url);

        if (headers != null && !headers.isEmpty()) {
            headers.forEach(producerBuilder::setHeader);
        }

        if (params != null && !params.isEmpty()) {
            List<NameValuePair> nameValuePairs = params.entrySet().stream()
                    .map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
                    .collect(Collectors.toList());
            producerBuilder.setEntity(AsyncEntityProducers.createUrlEncoded(nameValuePairs, StandardCharsets.UTF_8));
        }

        return execute(producerBuilder);
    }

    private CompletableFuture<SimpleHttpResponse> execute(AsyncRequestBuilder requestBuilder) {
        CompletableFuture<SimpleHttpResponse> future = new CompletableFuture<>();
        AsyncResponseConsumer<SimpleHttpResponse> respConsumer = SimpleResponseConsumer.create();
        getClient().execute(requestBuilder.build(), respConsumer, new FutureCallback<SimpleHttpResponse>() {
            @Override
            public void completed(SimpleHttpResponse httpResponse) {
                future.complete(httpResponse);
            }

            @Override
            public void failed(Exception e) {
                future.completeExceptionally(e);
            }

            @Override
            public void cancelled() {
                future.cancel(true);
            }
        });

        return future;
    }


    private CloseableHttpAsyncClient getClient() {
        if (client != null) {
            return client;
        }

        synchronized (this) {
            try {
                TrustManager[] trustManagers = new TrustManager[]{new NoTrustManager()};
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, trustManagers, new SecureRandom());

                IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
                        .setSoTimeout(3000, TimeUnit.MILLISECONDS)
                        .build();

                TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create()
                        .setHostnameVerifier(new NoHostnameVerifier())
                        .setSslContext(sslContext)
                        .build();

                PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create()
                        .setMaxConnPerRoute(1000)
                        .setMaxConnTotal(2000)
                        .setTlsStrategy(tlsStrategy)
                        .build();

                CloseableHttpAsyncClient newClient = HttpAsyncClients.custom()
                        .setIOReactorConfig(ioReactorConfig)
                        .setConnectionManager(connectionManager)
                        .build();
                newClient.start();
                this.client = newClient;
            } catch (NoSuchAlgorithmException | KeyManagementException e) {
                throw new RuntimeException("初始化HTTP客户端时发生错误", e);
            }
        }

        return client;
    }

    private static class NoTrustManager implements X509TrustManager {

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }

    private static class NoHostnameVerifier implements HostnameVerifier {

        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

}
